// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_PARSING_PREPARSE_DATA_IMPL_H_
#define V8_PARSING_PREPARSE_DATA_IMPL_H_

#include "src/parsing/preparse-data.h"

#include "src/assert-scope.h"

namespace v8 {
namespace internal {

    // Classes which are internal to prepared-scope-data.cc, but are exposed in
    // a header for tests.

    // Wraps a ZoneVector<uint8_t> to have with functions named the same as
    // PodArray<uint8_t>.
    class ZoneVectorWrapper {
    public:
        ZoneVectorWrapper() = default;
        explicit ZoneVectorWrapper(ZoneVector<uint8_t>* data)
            : data_(data)
        {
        }

        int data_length() const { return static_cast<int>(data_->size()); }

        uint8_t get(int index) const { return data_->at(index); }

    private:
        ZoneVector<uint8_t>* data_ = nullptr;
    };

    template <class Data>
    class BaseConsumedPreparseData : public ConsumedPreparseData {
    public:
        class ByteData : public PreparseByteDataConstants {
        public:
            ByteData() { }

            // Reading from the ByteData is only allowed when a ReadingScope is on the
            // stack. This ensures that we have a DisallowHeapAllocation in place
            // whenever ByteData holds a raw pointer into the heap.
            class ReadingScope {
            public:
                ReadingScope(ByteData* consumed_data, Data data)
                    : consumed_data_(consumed_data)
                {
                    consumed_data->data_ = data;
#ifdef DEBUG
                    consumed_data->has_data_ = true;
#endif
                }
                explicit ReadingScope(BaseConsumedPreparseData<Data>* parent)
                    : ReadingScope(parent->scope_data_.get(), parent->GetScopeData())
                {
                }
                ~ReadingScope()
                {
#ifdef DEBUG
                    consumed_data_->has_data_ = false;
#endif
                }

            private:
                ByteData* consumed_data_;
                DISALLOW_HEAP_ALLOCATION(no_gc)
            };

            void SetPosition(int position)
            {
                DCHECK_LE(position, data_.data_length());
                index_ = position;
            }

            size_t RemainingBytes() const
            {
                DCHECK(has_data_);
                DCHECK_LE(index_, data_.data_length());
                return data_.data_length() - index_;
            }

            bool HasRemainingBytes(size_t bytes) const
            {
                DCHECK(has_data_);
                return index_ <= data_.data_length() && bytes <= RemainingBytes();
            }

            int32_t ReadUint32()
            {
                DCHECK(has_data_);
                DCHECK(HasRemainingBytes(kUint32Size));
                // Check that there indeed is an integer following.
                DCHECK_EQ(data_.get(index_++), kUint32Size);
                int32_t result = data_.get(index_) + (data_.get(index_ + 1) << 8) + (data_.get(index_ + 2) << 16) + (data_.get(index_ + 3) << 24);
                index_ += 4;
                stored_quarters_ = 0;
                return result;
            }

            int32_t ReadVarint32()
            {
                DCHECK(HasRemainingBytes(kVarint32MinSize));
                DCHECK_EQ(data_.get(index_++), kVarint32MinSize);
                int32_t value = 0;
                bool has_another_byte;
                unsigned shift = 0;
                do {
                    uint8_t byte = data_.get(index_++);
                    value |= static_cast<int32_t>(byte & 0x7F) << shift;
                    shift += 7;
                    has_another_byte = byte & 0x80;
                } while (has_another_byte);
                DCHECK_EQ(data_.get(index_++), kVarint32EndMarker);
                stored_quarters_ = 0;
                return value;
            }

            uint8_t ReadUint8()
            {
                DCHECK(has_data_);
                DCHECK(HasRemainingBytes(kUint8Size));
                // Check that there indeed is a byte following.
                DCHECK_EQ(data_.get(index_++), kUint8Size);
                stored_quarters_ = 0;
                return data_.get(index_++);
            }

            uint8_t ReadQuarter()
            {
                DCHECK(has_data_);
                if (stored_quarters_ == 0) {
                    DCHECK(HasRemainingBytes(kUint8Size));
                    // Check that there indeed are quarters following.
                    DCHECK_EQ(data_.get(index_++), kQuarterMarker);
                    stored_byte_ = data_.get(index_++);
                    stored_quarters_ = 4;
                }
                // Read the first 2 bits from stored_byte_.
                uint8_t result = (stored_byte_ >> 6) & 3;
                DCHECK_LE(result, 3);
                --stored_quarters_;
                stored_byte_ <<= 2;
                return result;
            }

        private:
            Data data_ = {};
            int index_ = 0;
            uint8_t stored_quarters_ = 0;
            uint8_t stored_byte_ = 0;
#ifdef DEBUG
            bool has_data_ = false;
#endif
        };

        BaseConsumedPreparseData()
            : scope_data_(new ByteData())
            , child_index_(0)
        {
        }

        virtual Data GetScopeData() = 0;

        virtual ProducedPreparseData* GetChildData(Zone* zone, int child_index) = 0;

        ProducedPreparseData* GetDataForSkippableFunction(
            Zone* zone, int start_position, int* end_position, int* num_parameters,
            int* function_length, int* num_inner_functions, bool* uses_super_property,
            LanguageMode* language_mode) final;

        void RestoreScopeAllocationData(DeclarationScope* scope) final;

#ifdef DEBUG
        bool VerifyDataStart();
#endif

    private:
        void RestoreDataForScope(Scope* scope);
        void RestoreDataForVariable(Variable* var);
        void RestoreDataForInnerScopes(Scope* scope);

        std::unique_ptr<ByteData> scope_data_;
        // When consuming the data, these indexes point to the data we're going to
        // consume next.
        int child_index_;

        DISALLOW_COPY_AND_ASSIGN(BaseConsumedPreparseData);
    };

    // Implementation of ConsumedPreparseData for on-heap data.
    class OnHeapConsumedPreparseData final
        : public BaseConsumedPreparseData<PreparseData> {
    public:
        OnHeapConsumedPreparseData(Isolate* isolate, Handle<PreparseData> data);

        PreparseData GetScopeData() final;
        ProducedPreparseData* GetChildData(Zone* zone, int child_index) final;

    private:
        Isolate* isolate_;
        Handle<PreparseData> data_;
    };

    // A serialized PreparseData in zone memory (as apposed to being on-heap).
    class ZonePreparseData : public ZoneObject {
    public:
        V8_EXPORT_PRIVATE ZonePreparseData(Zone* zone, Vector<uint8_t>* byte_data,
            int child_length);

        Handle<PreparseData> Serialize(Isolate* isolate);

        int children_length() const { return static_cast<int>(children_.size()); }

        ZonePreparseData* get_child(int index) { return children_[index]; }

        void set_child(int index, ZonePreparseData* child)
        {
            DCHECK_NOT_NULL(child);
            children_[index] = child;
        }

        ZoneVector<uint8_t>* byte_data() { return &byte_data_; }

    private:
        ZoneVector<uint8_t> byte_data_;
        ZoneVector<ZonePreparseData*> children_;

        DISALLOW_COPY_AND_ASSIGN(ZonePreparseData);
    };

    ZonePreparseData* PreparseDataBuilder::ByteData::CopyToZone(
        Zone* zone, int children_length)
    {
        DCHECK(is_finalized_);
        return new (zone) ZonePreparseData(zone, &zone_byte_data_, children_length);
    }

    // Implementation of ConsumedPreparseData for PreparseData
    // serialized into zone memory.
    class ZoneConsumedPreparseData final
        : public BaseConsumedPreparseData<ZoneVectorWrapper> {
    public:
        ZoneConsumedPreparseData(Zone* zone, ZonePreparseData* data);

        ZoneVectorWrapper GetScopeData() final;
        ProducedPreparseData* GetChildData(Zone* zone, int child_index) final;

    private:
        ZonePreparseData* data_;
        ZoneVectorWrapper scope_data_wrapper_;
    };

} // namespace internal
} // namespace v8

#endif // V8_PARSING_PREPARSE_DATA_IMPL_H_
